﻿/*
Copyright © 2011 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.IO;

namespace Simple.Storage.Serializer
{
    /// <summary>
    /// Base class for the serializers - provides the parameter checks for all methods.
    /// </summary>
    public abstract class AbstractSerializer : ISerializer
    {
        /// <summary>
        /// Checks if the serializer is associated to a specific type.
        /// </summary>
        /// <param name="type">The checked type.</param>
        /// <returns>
        /// <c>true</c> if the serializer can work with instances of <paramref name="type"/>;
        /// <c>false</c> otherwise.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="type"/> parameter is <c>null</c>.
        /// </exception>
        public bool CanSerialize(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            return this.InternalCanSerialize(type);
        }

        /// <summary>
        /// Checks whether the <see cref="ISerializer.Deserialize(BinaryReader, Type, object)"/>
        /// method is implemented by this serializer.
        /// </summary>
        /// <param name="type">The checked type.</param>
        /// <returns>
        /// <c>true</c> if the serializer can work with initialized instances of
        /// <paramref name="type"/>; <c>false</c> otherwise.
        /// </returns>
        /// <remarks>
        /// This method should not return <c>true</c> when <see cref="ISerializer.CanSerialize"/>
        /// returns <c>false</c>
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="type"/> parameter is <c>null</c>.
        /// </exception>
        public bool CanWorkInPlace(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            return this.InternalCanWorkInPlace(type);
        }

        /// <summary>
        /// Serializes a specified object as a binary stream.
        /// </summary>
        /// <param name="writer">
        /// The writer associated with the target stream.
        /// </param>
        /// <param name="type">
        /// The reference type for the serialization.
        /// </param>
        /// <param name="value">
        /// The serialized value - should be an instance of <paramref name="type"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// One of the <paramref name="writer"/> or <paramref name="type"/> parameters
        /// is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="value"/> is not an instance of <paramref name="type"/>.
        /// </exception>
        /// <exception cref="System.Runtime.Serialization.SerializationException">
        /// The <paramref name="value"/> can't be serialized.
        /// </exception>
        public void Serialize(BinaryWriter writer, Type type, object value)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }
            else if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            else if (value != null && !type.IsInstanceOfType(value))
            {
                string message = string.Format(
                    Messages.Culture,
                    Messages.AbstractSerializerArgument,
                    type.FullName,
                    value.GetType().FullName);
                throw new ArgumentException(message, "value");
            }

            this.InternalSerialize(writer, type, value);
        }

        /// <summary>
        /// Deserializes an object from a binary stream.
        /// </summary>
        /// <param name="reader">
        /// The reader associated with the source stream.
        /// </param>
        /// <param name="type">
        /// The reference type for the serialization.
        /// </param>
        /// <returns>
        /// The deserialized object.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="reader"/> parameter is <c>null</c>.
        /// </exception>
        /// <exception cref="System.Runtime.Serialization.SerializationException">
        /// The value can't be deserialized.
        /// </exception>
        public object Deserialize(BinaryReader reader, Type type)
        {
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }
            else if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            return this.InternalDeserialize(reader, type);
        }

        /// <summary>
        /// Deserializes an object from a binary stream into an existing object.
        /// </summary>
        /// <param name="reader">
        /// The reader associated with the source stream.
        /// </param>
        /// <param name="type">
        /// The reference type for the serialization.
        /// </param>
        /// <param name="value">
        /// The contener for the deserialized object.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// One of the parameters is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="value"/> is not an instance of <paramref name="type"/>.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// This deserialization mode is not supported by the current implementation.
        /// </exception>
        /// <exception cref="System.Runtime.Serialization.SerializationException">
        /// The value can't be deserialized.
        /// </exception>
        /// <seealso cref="ISerializer.CanWorkInPlace"/>
        public void Deserialize(BinaryReader reader, Type type, object value)
        {
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }
            else if (type == null)
            {
                throw new ArgumentNullException("type");
            }
            else if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            else if (!type.IsInstanceOfType(value))
            {
                string message = string.Format(
                    Messages.Culture,
                    Messages.AbstractSerializerArgument,
                    type.FullName,
                    value.GetType().FullName);
                throw new ArgumentException(message, "value");
            }

            this.InternalDeserialize(reader, type, value);
        }

        /// <summary>
        /// Implements the <see cref="ISerializer.CanSerialize"/> logic.
        /// </summary>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <returns>
        /// <c>true</c> if the serializer can work with instances of <paramref name="type"/>;
        /// <c>false</c> otherwise.
        /// </returns>
        protected abstract bool InternalCanSerialize(Type type);

        /// <summary>
        /// Implements the <see cref="ISerializer.CanWorkInPlace"/> logic.
        /// </summary>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <returns>
        /// <c>true</c> if the serializer can work with initialized instances of
        /// <paramref name="type"/>; <c>false</c> otherwise.
        /// </returns>
        protected abstract bool InternalCanWorkInPlace(Type type);

        /// <summary>
        /// Implements the serialization logic.
        /// </summary>
        /// <param name="writer">
        /// The writer - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="value">
        /// The value to serialize - guaranty to be not <c>null</c> and associated with
        /// the current serializer.
        /// </param>
        protected abstract void InternalSerialize(BinaryWriter writer, Type type, object value);

        /// <summary>
        /// Implements the deserialization logic.
        /// </summary>
        /// <param name="reader">
        /// The reader - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <returns>The deserialized value.</returns>
        protected abstract object InternalDeserialize(BinaryReader reader, Type type);

        /// <summary>
        /// Implements the in-place deserialization logic.
        /// </summary>
        /// <param name="reader">
        /// The reader - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="value">
        /// The container for the deserialized object - guaranty to be not <c>null</c>.
        /// </param>
        protected abstract void InternalDeserialize(BinaryReader reader, Type type, object value);
    }
}
